import threading
from typing import Callable
import traceback
import time
import socket

from pymud import Session


class MudBroker():

    def __init__(self, server_ip, port, session: Session, max_conn=10) -> None:
        self.server_ip = server_ip
        self.port = port
        self.session = session
        self.max_conn = max_conn
        self.clients = []
        self._set_socket()

    def run(self, callback: Callable):
        self.main_exit_flag = threading.Event()
        self.callback = callback
        self.main_thread = threading.Thread(target=self.wait_for_conn)
        self.main_thread.start()

    def stop(self):
        print("wait for main thread exit...")
        self.main_exit_flag.set()
        self.main_thread.join()
        print("main thread exit")

    def send_mud(self, msg: str):
        if not len(self.clients):
            self.session.info("无连接中的客户端")

        recycle_clients = []
        for client in self.clients:
            conn = client["conn"]
            try:
                conn[0].send(msg.encode("utf-8"))
            except:
                recycle_clients.append(client)

        for client in recycle_clients:
            client["exit"].set()
            client["thread"].join()
            client["conn"][0].close()
            self.clients.remove(client)

    def wait_for_conn(self):
        conn_cnt = 1
        while True:
            if self.main_exit_flag.is_set():
                print("wait for client thread exit")
                self.clean()
                print("all client threads exit")
                break

            try:
                conn = self.server.accept()
            except socket.timeout:
                continue
            except Exception as e:
                print(str(e))
                traceback.print_exc()
                break

            client = {}
            client["conn"] = conn
            client["exit"] = threading.Event()
            client["thread"] = threading.Thread(target=self.receive,
                                                args=(
                                                    client["conn"],
                                                    client["exit"],
                                                ))
            client["thread"].start()
            self.clients.append(client)
            self.session.info(f"接受到第 {conn_cnt} 个连接")

    def receive(self, conn, exit_flag):
        while True:
            if exit_flag.is_set():
                conn[0].close()
                break

            try:
                data = conn[0].recv(4096)
            except socket.timeout:
                continue
            except:
                conn[0].close()
                break

            try:
                if len(data):
                    self.callback(data.decode("utf-8"))
            except Exception as e:
                print(str(e))
                traceback.print_exc()

    def clean(self):
        for client in self.clients:
            client["exit"].set()
            client["thread"].join()
            client["conn"][0].close()

        self.clients.clear()
        self.server.close()

    def _set_socket(self):
        self.server = socket.socket()
        self.server.settimeout(5)
        self.server.bind((self.server_ip, self.port))
        self.server.listen(self.max_conn)
